package analytics;

import java.rmi.RemoteException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import management.ClientEventReceiver;
import event.AuctionEvent;
import event.BidEvent;
import event.Event;
import event.StatisticsEvent;
import event.UserEvent;

/**
 * Implements the interface {@link AnalyticsServer}.
 * 
 * @author Lukas
 *
 */
public class AnalyticsServerImpl implements AnalyticsServer {
	private ConcurrentHashMap<String, Subscription> subscriptions = new ConcurrentHashMap<String, Subscription>();
	private ConcurrentHashMap<Long, AnalAuction> auctions = new ConcurrentHashMap<Long, AnalAuction>();
	private ConcurrentHashMap<String, Long> users = new ConcurrentHashMap<String, Long>();
	
	private int auctionCount, sessionCount, successfulAuctions, bidCount, subscriptionCount;
	private double avgAuctionTime, avgUserTime, minUserTime, maxUserTime, maxBid;
	private long startTime;
	
	/**
	 * Constructor
	 */
	public AnalyticsServerImpl() {
		auctionCount = 0;
		sessionCount = 0;
		successfulAuctions = 0;
		bidCount = 0;
		subscriptionCount = 0;
		avgAuctionTime = 0d;
		avgUserTime = 0d;
		minUserTime = Long.MAX_VALUE;
		maxUserTime = 0d;
		maxBid = 0d;
		startTime = System.currentTimeMillis();
	}

	@Override
	public String subscribe(String filter, ClientEventReceiver receiver) throws RemoteException {
		String id = Integer.toString(++subscriptionCount);
		Subscription s = new Subscription(filter, receiver);
		subscriptions.put(id, s);
		return id;
	}

	@Override
	public boolean unsubscribe(String id) throws RemoteException {
		return subscriptions.remove(id) != null;
	}

	@Override
	public void processEvent(Event event) throws RemoteException {
		HashSet<ClientEventReceiver> alreadyNotified = new HashSet<ClientEventReceiver>();
		Iterator<Entry<String, Subscription>> iter = subscriptions.entrySet().iterator();
		while (iter.hasNext()) {
			Subscription s = iter.next().getValue();
			if (!alreadyNotified.contains(s.getReceiver()) && event.getType().matches(s.getFilter())) {
				//forward to this client
				try {
					s.getReceiver().processEvent(event);
					alreadyNotified.add(s.getReceiver());
				} catch (RemoteException e) {
					iter.remove();
				}
			}
		}
		createAggregatedEvents(event);
	}

	private void createAggregatedEvents(Event event) throws RemoteException {
		if (event instanceof AuctionEvent) {
			processAuctionEvent((AuctionEvent)event);
		} else if (event instanceof UserEvent) {
			processUserEvent((UserEvent)event);
		} else if (event instanceof BidEvent) {
			processBidEvent((BidEvent)event);
		}
	}
	
	private void processAuctionEvent(AuctionEvent event) throws RemoteException {
		if (event.getType().equals("AUCTION_STARTED")) {
			AnalAuction aa = new AnalAuction(event.getTimestamp());
			auctions.put(event.getAuctionID(), aa);
		} else if (event.getType().equals("AUCTION_ENDED")) {
			AnalAuction endedAuction = auctions.remove(event.getAuctionID());
			
			//AUCTION_TIME_AVG///////////////////////////////////////////////////////////
			StatisticsEvent se = new StatisticsEvent();
			se.setId(new java.rmi.server.UID().toString());
			se.setTimestamp(event.getTimestamp());
			se.setType("AUCTION_TIME_AVG");
			long duration = event.getTimestamp() - endedAuction.getStart();
			avgAuctionTime = (avgAuctionTime * auctionCount + duration) / ((double)(auctionCount+1));
			se.setValue(avgAuctionTime);
			processEvent(se);
			
			//AUCTION_SUCCESS_RATIO//////////////////////////////////////////////////////
			se.setId(new java.rmi.server.UID().toString());
			se.setType("AUCTION_SUCCESS_RATIO");
			if (endedAuction.isBid()) {
				successfulAuctions++;
			}
			se.setValue((double)successfulAuctions / (double)++auctionCount);
			processEvent(se);
		}
	}
	
	private void processUserEvent(UserEvent event) throws RemoteException {
		if (event.getType().equals("USER_LOGIN")) {
			users.put(event.getUserName(), event.getTimestamp());
		} else if (event.getType().equals("USER_LOGOUT") || event.getType().equals("USER_DISCONNECTED")) {
			long duration = event.getTimestamp() - users.get(event.getUserName());

			//USER_SESSIONTIME_AVG///////////////////////////////////////////////////////
			StatisticsEvent se = new StatisticsEvent();
			se.setId(new java.rmi.server.UID().toString());
			se.setTimestamp(event.getTimestamp());
			se.setType("USER_SESSIONTIME_AVG");
			avgUserTime = (avgUserTime * sessionCount + duration) / ((double)(sessionCount+1));
			sessionCount++;
			se.setValue(avgUserTime);
			processEvent(se);

			//USER_SESSIONTIME_MIN///////////////////////////////////////////////////////
			if (duration < minUserTime) {
				minUserTime = duration;
				se.setId(new java.rmi.server.UID().toString());
				se.setType("USER_SESSIONTIME_MIN");
				se.setValue(duration);
				processEvent(se);
			}

			//USER_SESSIONTIME_MAX///////////////////////////////////////////////////////
			if (duration > maxUserTime) {
				maxUserTime = duration;
				se.setId(new java.rmi.server.UID().toString());
				se.setType("USER_SESSIONTIME_MAX");
				se.setValue(duration);
				processEvent(se);
			}
		}
	}
	
	private void processBidEvent(BidEvent event) throws RemoteException {
		if (event.getType().equals("BID_PLACED")) {
			auctions.get(event.getAuctionID()).setBid(true);
		}
		if (event.getType().equals("BID_PLACED") || (event.getType().equals("BID_OVERBID"))) {
			StatisticsEvent se = new StatisticsEvent();
			se.setTimestamp(event.getTimestamp());
			
			//BID_PRICE_MAX//////////////////////////////////////////////////////////////
			if (event.getPrice() > maxBid) {
				maxBid = event.getPrice();
				se.setId(new java.rmi.server.UID().toString());
				se.setType("BID_PRICE_MAX");
				se.setValue(maxBid);
				processEvent(se);
			}

			//BID_COUNT_PER_MINUTE///////////////////////////////////////////////////////
			se.setId(new java.rmi.server.UID().toString());
			se.setType("BID_COUNT_PER_MINUTE");
			double minutes = ((double) (event.getTimestamp() - startTime)) / 60000d;
			se.setValue(((double)++bidCount) / minutes);
			processEvent(se);
		}
	}
}
